/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.xml.stream;

import java.util.ListIterator;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EntityDeclaration;
import javax.xml.stream.events.EntityReference;
import javax.xml.stream.events.XMLEvent;

/** Wraps a {@link ListIterator} of {@link XMLEvent}s with an {@link XMLEventReader} */
public class XMLEventBufferReader implements XMLEventReader {
    private final ListIterator<XMLEvent> eventBuffer;
    private XMLEvent previousEvent;

    public XMLEventBufferReader(ListIterator<XMLEvent> eventBuffer) {
        this.eventBuffer = eventBuffer;
    }

    @Override
    public void close() {
        // NO-OP
    }

    @Override
    public XMLEvent peek() {
        final XMLEvent event = this.eventBuffer.next();
        // Step back by one in the list
        this.eventBuffer.previous();

        return event;
    }

    @Override
    public boolean hasNext() {
        return this.eventBuffer.hasNext();
    }

    @Override
    public XMLEvent next() {
        this.previousEvent = this.eventBuffer.next();
        return this.previousEvent;
    }

    @Override
    public void remove() {
        this.eventBuffer.remove();
    }

    @Override
    public String getElementText() throws XMLStreamException {
        XMLEvent event = this.previousEvent;
        if (event == null) {
            throw new XMLStreamException(
                    "Must be on START_ELEMENT to read next text, element was null");
        }
        if (!event.isStartElement()) {
            throw new XMLStreamException(
                    "Must be on START_ELEMENT to read next text", event.getLocation());
        }

        final StringBuilder text = new StringBuilder();
        while (!event.isEndDocument()) {
            switch (event.getEventType()) {
                case XMLStreamConstants.CHARACTERS:
                case XMLStreamConstants.SPACE:
                case XMLStreamConstants.CDATA:
                    {
                        final Characters characters = event.asCharacters();
                        text.append(characters.getData());
                        break;
                    }
                case XMLStreamConstants.ENTITY_REFERENCE:
                    {
                        final EntityReference entityReference = (EntityReference) event;
                        final EntityDeclaration declaration = entityReference.getDeclaration();
                        text.append(declaration.getReplacementText());
                        break;
                    }
                case XMLStreamConstants.COMMENT:
                case XMLStreamConstants.PROCESSING_INSTRUCTION:
                    {
                        // Ignore
                        break;
                    }
                default:
                    {
                        throw new XMLStreamException(
                                "Unexpected event type '"
                                        + XMLStreamConstantsUtils.getEventName(event.getEventType())
                                        + "' encountered. Found event: "
                                        + event,
                                event.getLocation());
                    }
            }

            event = this.nextEvent();
        }

        return text.toString();
    }

    @Override
    public Object getProperty(String name) throws IllegalArgumentException {
        // TODO no idea how to cache these :(
        return null;
    }

    @Override
    public XMLEvent nextEvent() throws XMLStreamException {
        return this.next();
    }

    @Override
    public XMLEvent nextTag() throws XMLStreamException {
        XMLEvent event = this.nextEvent();
        while ((event.isCharacters() && event.asCharacters().isWhiteSpace())
                || event.isProcessingInstruction()
                || event.getEventType() == XMLStreamConstants.COMMENT) {

            event = this.nextEvent();
        }

        if (!event.isStartElement() && event.isEndElement()) {
            throw new XMLStreamException(
                    "Unexpected event type '"
                            + XMLStreamConstantsUtils.getEventName(event.getEventType())
                            + "' encountered. Found event: "
                            + event,
                    event.getLocation());
        }

        return event;
    }
}
